#!/usr/bin/perl

####################################################
# Copyright (c) 2002-2007 BioWare Corp.
# http://www.bigdaddymonitor.org
#
# This file is part of the BigDaddy Monitoring
# program. It is free software distributed under
# the terms of the BSD License. It is ditributed
# in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY; without event the implied
# warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
#
# The BSD License is available from:
# http://www.opensource.org/licenses/bsd-license.php
####################################################

#===================================================================================================
# BigDaddy CronTab Monitor Version 3.0
# Main Monitor for the Crontab Control
# Dups 24.03.2004
# Dups 14.04.2006 <dups@bioware.com>
#===================================================================================================

BEGIN
{
	push(@INC,'/bigdaddy')
}

use strict;
use DBI;
use POSIX;
use Config::IniFiles;
use BioMain qw(:allfunctions);

my ($start_val) = @ARGV;

#===================================================================================================
# Main
#===================================================================================================
if ($start_val eq "stop")
{
	system ("killall crontab_monitor > /dev/null 2>&1");
	exit;

}

#Before we do anything we are going to bring up the main ini file: bigdaddy.ini should be in the
#same dir as these executables.
my $bigdaddy_cfg= new Config::IniFiles( -file => "/bigdaddy/bigdaddy.ini" );

my $this_process_name = $bigdaddy_cfg->val('main', 'thisprocessname');

my $dbIniHandle;

#Get the processing configuration
my $bdprocessing = $bigdaddy_cfg->val('main', 'bdprocessing');

#Get the dbini handle
#This is dependent on what kind of process is running
if ($bdprocessing eq "single" || $bdprocessing eq "multimaster")
{
	
	$dbIniHandle = $bigdaddy_cfg->val('main', 'dbinihandle');
}
elsif ($bdprocessing eq "masterslave")
{
	if ($bigdaddy_cfg->val('masterslave', 'thisinstance') eq "master")
	{
		$dbIniHandle = $bigdaddy_cfg->val('masterslave', 'masterdbhandle');
	}
	
	if ($bigdaddy_cfg->val('masterslave', 'thisinstance') eq "slave")
	{
		$dbIniHandle = $bigdaddy_cfg->val('masterslave', 'slavedbhandle');
	}
}

#Require the common Bigdaddy functions etc.
require ("/bigdaddy/bigdaddy_common.pl");

#Get the mailserver
my $mailServer = $bigdaddy_cfg->val('main', 'smtpserver');

if (!defined($mailServer) || $mailServer eq '')
{
	$mailServer = "localhost";
}

#Get the Global From Email
my $fromEmail = $bigdaddy_cfg->val('main', 'fromemail');

my $logFile="/bigdaddy/logs/crontab_monitor.log";
my $processFile="/bigdaddy/logs/crontab_monitor.process";
check_logfile($logFile, "Crontab Monitor");

&daemonize;
#make a child

my $bReturn=0;
my $nScriptRunTime;

START: while(1)
{
	my $nStartTime=time();
	
	my $crontabIni="crontab";
	my $sID = "crontabmon";
	
	#===================================================================================================
	# Processing configuration
	#===================================================================================================
	if ($bdprocessing ne "single")
	{
		if ($bdprocessing eq "masterslave")
		{
			$bReturn = CheckMasterSlave($bigdaddy_cfg, $sID);
		}
		
		if ($bdprocessing eq "multimaster")
		{
			$bReturn = CheckMultiMaster($bigdaddy_cfg, $dbIniHandle, $this_process_name, $sID);
		}
			
		if ($bReturn == 1)
		{
			$nScriptRunTime = time() - $nStartTime;
			#Open a Log File and write in the current status on this server
			&SetProcessTime($processFile);
			sleep(60 - $nScriptRunTime);
			goto START;
		}
		
		if ($bReturn == 2)
		{
			goto CONTINUE;
		}

	}

	CONTINUE:
	
	my $dbb = callfor_db_handle($dbIniHandle, 0, 99);
	
	my $sql="UPDATE report SET report='".time()."', processID = '".$this_process_name."' WHERE ID='".$sID."'";
	$dbb->do( $sql );
	
	#Open a Log File and write in the current status on this server
	&SetProcessTime($processFile);

	$sql="SELECT inifile, ID FROM inifile WHERE ID IN ('".$crontabIni."')";
	#print $sql."\n";
	my $result=$dbb->prepare( $sql );
	$result->execute;

	while (my $ref=$result->fetchrow_hashref())
	{
	    open (TMPINIFILE, ">/tmp/".$ref->{'ID'}.".ini2") or die "Could not open temp file for writing: $!";
	    print TMPINIFILE $ref->{'inifile'};
	    close (TMPINIFILE) or die "Could not close temp file: $!";
	}

	my $cfg_crontab= new Config::IniFiles( -file => "/tmp/".$crontabIni.".ini2" );

	# Check for the crontab delay value
	my $delay=$cfg_crontab->val('global','globaldelay');
	my $status=$cfg_crontab->val('global','globalstatus');

	if ($status eq "off" || ($status eq "delay" && $delay>time()) || $status eq "disablemonitoring")
	{
		#continue don't start up monitoring
		#Do any processing of off stuff
	}
	else
	{
		$sql="SELECT ID FROM cronjob WHERE active=1 and ignoreMonitor=0";
		my $result=$dbb->prepare($sql);
		$result->execute;

		while (my $ref=$result->fetchrow_hashref())
		{
			&makeNewChild($ref->{'ID'});
		}
	}

	# Check to see if the crontabs are globally disabled. If so
	# Disable the monitoring.

	#We only want to run this a minimum of every 60 seconds.
	my $nScriptRunTime=time()-$nStartTime;

	$dbb->disconnect;

	if ($nScriptRunTime<60)
	{
		sleep(60-$nScriptRunTime);
	}
}

#===================================================================================================
# Child Subroutine
# Forks child for each cronjob
#===================================================================================================
sub makeNewChild
{

	my ($cronID) = @_;
	my $sql;
	my $pid;
	die "fork: $!" unless defined ($pid = fork);

	if ($pid)
	{
		#parent process records the birth of the child and returns to the rest of the script
		#$children[$number]=$pid;
		#$SIG{CHLD} = 'IGNORE';
		#set a timer

		my $kid;
		do {
			$kid = waitpid($pid, &WNOHANG);
		} until $kid > 0;
		return;
	}
	else
	{


		#Create the database handle with no idle and no crash on errors
		my $dbh = callfor_db_handle($dbIniHandle, 0, 99);
		
		#Get the Phone Tags
		my %phoneTags;
		%phoneTags = get_phoneTags($dbh);

		$sql="SELECT ID, filename, last_ranenabled, iniID, mon_currentlevel, mon_endlevel, mon_threshold, mon_currentlevelstart, mon_lastcheck, mon_startlevel FROM cronjob WHERE ID=".$cronID;
		#print $sql."\n";
		my $result=$dbh->prepare($sql);
		$result->execute;

		while (my $ref=$result->fetchrow_hashref())
		{
			#is ranenabled-time() greater than mon_threshold
			my $thresholdVal=time()-$ref->{'last_ranenabled'};



			if ($thresholdVal>$ref->{'mon_threshold'} && $ref->{'last_ranenabled'}>0)
			{
				#We know now that this Cron is having a problem.
				#We need to get the cron ini
				my $cfg=get_inicfg( $ref->{'iniID'}, $cronID, $dbh );

				# Is the currentlevel 0?
				if ($ref->{'mon_currentlevel'}==0)
				{
					&doaction( $ref->{'iniID'}, $cronID, $ref->{'filename'}, $ref->{'mon_startlevel'}, $dbh, %phoneTags);
					&updateinfo( $cronID, $dbh, $ref->{'mon_startlevel'} );
				}

				# If the currentlevel is greater than 0
				if ($ref->{'mon_currentlevel'}>0)
				{
					#is this the final level?
					if ($ref->{'mon_currentlevel'}==5 || $ref->{'mon_currentlevel'}==$ref->{'mon_endlevel'})
					{
						#is it time to loop
						if ((time()-600)>$ref->{'mon_lastcheck'})
						{
							&doaction( $ref->{'iniID'}, $cronID, $ref->{'filename'}, $ref->{'mon_currentlevel'}, $dbh, %phoneTags);
							&setlastcheck($cronID, $dbh);
						}
					}
					else
					{
						#It's not the final level so check to see if it is time to escalate
						my $level="level".$ref->{'mon_currentlevel'};
						my $escalateTime=0;
						my $loopTime=0;
						if (defined($cfg->val( $level, 'escalate' )))
						{
							$escalateTime=$cfg->val( $level, 'escalate' );
						}

						if (defined($cfg->val( $level, 'loop' )))
						{
							$loopTime=$cfg->val( $level, 'loop' );
						}

						if ((time()-$escalateTime)>$ref->{'mon_currentlevelstart'})
						{
							#Time to escalate
							&doaction( $ref->{'iniID'}, $cronID, $ref->{'filename'}, ($ref->{'mon_currentlevel'}+1), $dbh, %phoneTags);
							&updateinfo( $cronID, $dbh, ($ref->{'mon_currentlevel'}+1));
						}
						elsif ((time()-$loopTime)>$ref->{'mon_lastcheck'})
						{
							#Time to Loop
							&doaction( $ref->{'iniID'}, $cronID, $ref->{'filename'}, $ref->{'mon_currentlevel'}, $dbh, %phoneTags);
							&setlastcheck($cronID, $dbh);
						}
					}
				}
			}
			else
			{
				$sql="UPDATE cronjob SET mon_currentlevel=0, mon_currentlevelstart=0, mon_lastcheck=0 WHERE ID=".$cronID;
				#print $sql."\n";
				$dbh->do( $sql );
			}
		}

		exit;
	} #end if
} #end sub



#===================================================================================================
# updateinfo(int $cronID, dbh $dbj, int $levelup)
# Updates the info for a cron to go up a level, sets the time for the level start
# And the lastcheck value which tracks when an action was last performed
#===================================================================================================
sub updateinfo
{
	my ($cronID, $dbh, $levelup)=@_;
	my $sql="UPDATE cronjob SET mon_currentlevel='".$levelup."', mon_currentlevelstart=".time().", mon_lastcheck=".time()." WHERE ID=".$cronID;
	$dbh->do( $sql );
}

#===================================================================================================
# setlastcheck(int $cronID, dbh $dbh)
# Sets only the last check value of when actions were performed for cron cronID
#===================================================================================================
sub setlastcheck
{
	my ($cronID, $dbh) = @_;
	my $sql="UPDATE cronjob SET mon_lastcheck=".time()." WHERE ID=".$cronID;
	$dbh->do( $sql );
}

#===================================================================================================
# doaction(str iniID, int $cronID, str $cronName, int $level, dbh $dbh)
# So the actions specified in the ini for this level, this cron
#===================================================================================================
sub doaction
{
	my ($iniID, $cronID, $cronName, $level, $dbh, %phoneTags) = @_;
	my $cfg=get_inicfg( $iniID, $cronID, $dbh );
	
	#Get the current time string
	my $sTimeString = localtime;
	
	my $bUseTags;
	#Check to see if this ini file uses tags or email addresses
	if (defined($cfg->val( 'global','usetags' )) && $cfg->val( 'global','usetags' ) eq "yes")
	{
		$bUseTags = 1;
	}
	
	#print $cfg->val( "level".$level,'email' )."\n";
	if (defined($cfg->val( "level".$level,'email' )))
	{

		my $messageString="Cron Fail  / Level: ".$level."\n";
		if (defined($cfg->val( "level".$level,'message' )))
		{
			$messageString.=$cfg->val( "level".$level,'message' )."\n";
		}
		$messageString.=$sTimeString;
		
		my $subjectString = "BD".$level." - CRON - ".$cronName;
		#logEntry("Action Performed: ".$subjectString, $logFile, 0);
		
		my @emailTags;
		
		if ($bUseTags == 1)
		{
			@emailTags = split(/,/, $cfg->val( "level".$level,'email' ));
			
			for (my $i = 0; $i<scalar(@emailTags); $i++)
			{
				my $sEmail = $phoneTags{trim($emailTags[$i])};
				&send_mail($sEmail, $messageString, $subjectString, $fromEmail, $mailServer  );
			}
		}
		else
		{
			&send_mail($cfg->val( "level".$level,'email' ), $messageString, $subjectString, $fromEmail, $mailServer );
		}
	}
}



#===================================================================================================
# get_inicfg(str iniID, int cronID, dbh dbh)
# Gets the inifile for that cron
#===================================================================================================
sub get_inicfg
{
	my ($iniID, $cronID, $dbh) = @_;
	my $sql="SELECT inifile, ID FROM inifile WHERE ID='".$iniID."'";
	my $result=$dbh->prepare( $sql );
	$result->execute;

	my $filenameAdd="cronID".$cronID."_".time();
	my $inifileName;
	while (my $ref=$result->fetchrow_hashref())
	{
		$inifileName="/tmp/".$ref->{'ID'}.".ini.".$filenameAdd;
		open (TMPINIFILE, ">$inifileName") or die "Could not open temp file for writing: $!";
		print TMPINIFILE $ref->{'inifile'};
		close (TMPINIFILE) or die "Could not close temp file: $!";
	}

	my $cfg= new Config::IniFiles( -file => $inifileName );
	unlink($inifileName);
	return $cfg;
}
#===================================================================================================
# get_phoneTags
# Gets the phone tags and places them into a hash
#===================================================================================================
sub get_phoneTags
{
	my ($dbh) = @_;
	my %hash;
	
	my $sql="SELECT ID, phoneNumber FROM phoneTag";
	my $result=$dbh->prepare( $sql );
	$result->execute;
	while (my $ref=$result->fetchrow_hashref())
	{
		$hash{$ref->{'ID'}} = $ref->{'phoneNumber'};
	}
	
	return %hash;
}



